Generated code - XML Webservices / WCF support

Preface

One hot buzzword of the last couple of years has to be 'webservices', which is short for XML Webservices, i.e.: XML message based services offered through a normal HTTP based website. As webservices are XML based, the .NET framework uses XmlSerializer to produce XML which reflects the objects returned from a webmethod and to produce instances of classes from the XML received, for example at the client. This section describes how to use the entity and entity collection classes of the generated code in a webservice scenario. Every entity and entity collection class implements IXmlSerializable, which makes it possible to transparently use these classes with webmethods, without the necessity to first produce XML from them using their WriteXml() methods.

The preferred way to implement webservices and .NET is by using WCF, or Windows Communication Foundation. LLBLGen Pro entities are fully usable with WCF similar to webservices. By default, LLBLGen Pro entity classes aren't marked with DataContract and DataMember attributes. If you want to have these defined, use the LLBLGen Pro designer's attribute feature to define the DataContract and DataMember attributes on entities and fields. Define these attributes in the Project Settings of the LLBLGen Pro designer.

For web services and WCF, which both use the IXmlSerializable implementations of the entity and entity collection classes, LLBLGen Pro uses its own Compact25 format, which is very lightweight and very fast to consume and produce as it contains almost no overhead. See for a description of the Compact25 format: generated code - Xml support.

Example usage

The example discussed here is pretty simple. It offers a service with three methods: GetCustomer, SaveCustomer and GetCustomers. The client consumes the service to retrieve a customer to have it edited in a winforms application and saves the changed data back into the database using the webservice, plus it uses the service to display all customers available.

The service project has references to both generated projects: the database generic and the database specific project. The client only has a reference to the database generic project, as it uses the service for database specific activity, namely the persistence logic to work with the actual data. Because both client and service have references to the database generic project, they both can use the same types for the entities, in this case the CustomerEntity.

The code below uses our low-level API, but you can also use Linq to LLBLGen Pro or QuerySpec to fetch the entities. The GetCustomers method returns a non-generic EntityCollection object as webservices can't deal with generics. 
The service
Below is the service code, simplified. As you can see, the code works directly with entity objects and entity collection objects.

// C#
[WebService(Namespace="http://www.llblgen.com/examples")]
public class CustomerService : System.Web.Services.WebService
{
	[WebMethod]
	public CustomerEntity GetCustomer(string customerID)
	{
		CustomerEntity toReturn = new CustomerEntity(customerID);
		using(DataAccessAdapter adapter = new DataAccessAdapter())
		{
			adapter.FetchEntity(toReturn);

			return toReturn;
		}
	}

	[WebMethod]
	public EntityCollection GetCustomers()
	{
		EntityCollection customers = new EntityCollection(new CustomerEntityFactory());
		using(DataAccessAdapter adapter = new DataAccessAdapter())
		{
			adapter.FetchEntityCollection(customers, null);
			return customers;
		}
	}

	[WebMethod]
	public bool SaveCustomer(CustomerEntity toSave)
	{
		using(DataAccessAdapter adapter = new DataAccessAdapter())
		{
			return adapter.SaveEntity(toSave);
		}
	}
}
' VB.NET
<WebService(Namespace="http://www.llblgen.com/examples")> _
Public Class CustomerService 
	Inherits System.Web.Services.WebService

	<WebMethod> _
	Public Function GetCustomer(customerID As String) As CustomerEntity
		Dim toReturn As New CustomerEntity(customerID)
		Dim adapter As New DataAccessAdapter()
		Try
			adapter.FetchEntity(toReturn)
			Return toReturn
		Finally
			adapter.Dispose()
		End Try
	End Function

	<WebMethod> _
	Public Function GetCustomers() As EntityCollection
		Dim customers As New EntityCollection(New CustomerEntityFactory())
		Dim adapter As New DataAccessAdapter()
		Try
			adapter.FetchEntityCollection(customers, Nothing)
			Return customers
		Finally
			adapter.Dispose()
		End Try
	End Function

	<WebMethod> _
	Public Function SaveCustomer(toSave As CustomerEntity) As Boolean
		Dim adapter As New DataAccessAdapter()
		Try
			Return adapter.SaveEntity(toSave)
		Finally
			adapter.Dispose()
		End Try
	End Function
End Class

This code forms the code behind of the .asmx file which forms the service entry point.

Custom Member serialization/deserialization

If you add your own member variables and properties to entity classes, you probably also want these values to be serialized and deserialized into the XML stream. Normally, a custom member exposed as a read/write property is serialized as a string using the ToString() method of the value of the custom property. In some cases this isn't sufficient and you want to perform your own custom xml serialization/deserialization on the value of this custom property, for example if this custom property represents a complex object.

To signal that the LLBLGen Pro runtime framework has to call custom xml serialization code for a given property, the property has to be annotated with a CustomXmlSerializationAttribute attribute. When a property is seen with that attribute, LLBLGen Pro will call the entity method entity.PerformCustomXmlSerialization to produce valid XML for the custom property. Likewise, when deserializing an XML stream into entities, the LLBLGen Pro runtime framework will call, when it runs into a property annotated with a CustomXmlSerializationAttribute, the method entity.PerformCustomXmlDeserialization to deserialize the xml for the property into a valid object. You should override these methods in a partial class of the entity which contains the custom properties.

Custom property serialization/deserialization is a feature of the Compact25 xml format, which is used by Adapter in Webservices/WCF scenarios.

Windows Communication Foundation (WCF) support

To be able to send entities from service to client and back using WCF, you have to define a ServiceContract. This ServiceContract defines the types involved in the service. It's recommended to define an interface onto which the ServiceContract is defined. Both client and service now know which types are involved in the service and no stub classes are created anymore nor necessary.

If you want to have a fixed DataContract instead, it might be better to send Data Transfer Objects (DTO)'s over the wire which are more or less dumb buckets with data back and forth instead of entity class instances. The reason is that a DataContract can't change however an entity might change over time, which then would violate the DataContract. LLBLGen Pro's powerful projection framework can help you with projecting fetched data onto DTO classes to send them over the wire.

If you want to return Entity class instances from a WCF service, use the LLBLGen Pro designer to add DataContract and DataMember attributes to entity, fields and navigator elements. The best way to do this is in the Project Settings. See the LLBLGen Pro designer manual for details.

Below is a small example of a simple WCF service and client. Its main purpose is to illustrate what to do to get LLBLGen Pro generated code working with WCF. You should check the MSDN library for information about WCF, configuration of WCF services and other WCF documentation to get a WCF service up and running in your environment.

note Note:

When sending entities over the wire using WCF, the 'IsNew' flag is not passed along as it is determinable on the service side for new entities. If you use the trick where you set the IsNew flag manually on an entity and then send the entity over the wire to the service, the IsNew flag is set to false at the service during deserialization, so you have to set it back to true in that special case scenario.

Interface with ServiceContract
Below is the service interface definition with the ServiceContract. The client code will use this interface to refer to the service and the service will use this interface to implement a common interface for clients to connect to.

// C#
[ServiceContract]
[ServiceKnownType(typeof(CustomerEntity))]
[ServiceKnownType(typeof(EntityCollection))]
public interface IWCFExample
{
    [OperationContract]
    IEntity2 GetCustomer(string customerID);

    [OperationContract]
    IEntityCollection2 GetCustomers();
}
' VB.NET
<ServiceContract(), _
ServiceKnownType(GetType(CustomerEntity)), _
ServiceKnownType(GetType(EntityCollection))> _
Public Interface IWCFExample
    <OperationContract()> _
    Function GetCustomer(customerID As String) As IEntity2 
    <OperationContract(&)gt; _
    Function GetCustomers() As IEntityCollection2 
End Interface
Server implementation
Below is the implementation of the IWCFExample interface to be used as a WCF service. As with the other examples above, the collection is a non-generic variant of EntityCollection, as WCF services can't deal with generic collection classes.

// C#
// class to implement the service logic
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class WCFExampleService : IWCFExample
{
    public IEntity2 GetCustomer(string customerID)
    {
        CustomerEntity toReturn = new CustomerEntity(customerID);
        using(DataAccessAdapter adapter = new DataAccessAdapter())
        {
            adapter.FetchEntity(toReturn);
        }
        return toReturn;
    }

    public IEntityCollection2 GetCustomers()
    {
        EntityCollection toReturn = new EntityCollection(new CustomerEntityFactory());
        using(DataAccessAdapter adapter = new DataAccessAdapter())
        {
            adapter.FetchEntityCollection(toReturn, null);
        }
        return toReturn;
    }
}

// class to actually run the service:
public class WCFExampleServerHost
{
    public WCFExampleServerHost()
    {
        WCFExampleService server = new WCFExampleService();
        ServiceHost host = new ServiceHost(server);
        host.Open();
    }
}
' VB.NET
' class to implement the service logic
<ServiceBehavior(InstanceContextMode := InstanceContextMode.Single)> _
Public Class WCFExampleService 
	Implements IWCFExample

    Public Function GetCustomer(customerID As string) As IEntity2 Implements IWCFExample.GetCustomer
        Dim toReturn As new CustomerEntity(customerID)
        Using adapter As New DataAccessAdapter()
            adapter.FetchEntity(toReturn)
        End Using
        Return toReturn
    End Function

    Public Function GetCustomers() As IEntityCollection2 Implements IWCFExample.GetCustomers
        Dim toReturn As New EntityCollection(New CustomerEntityFactory())
        Using adapter As New DataAccessAdapter()
            adapter.FetchEntityCollection(toReturn, Nothing)
        End Using
        Return toReturn
    End Function
End Class

' class to actually run the service:
Public Class WCFExampleServerHost
    Public Sub New()
        Dim server As New WCFExampleService()
        Dim host As New ServiceHost(server)
        host.Open()
    End Sub
End Class
Client usage of the service
Below is the code snippet to consume the service defined above. It illustrates the usage of the service.

// C#
ChannelFactory<IWCFExample> channelFactory =
        new ChannelFactory<IWCFExample>("WCFExampleServer");
IWCFExample server = channelFactory.CreateChannel();

// Fetch an entity
IEntity2 c = server.GetCustomer("CHOPS");

// Fetch a collection
IEntityCollection2 customers = serverTest.GetCustomers();
' VB.NET
Dim channelFactory As New ChannelFactory(Of IWCFExample)("WCFExampleServer")
Dim server As IWCFExample = channelFactory.CreateChannel()

' Fetch an entity
Dim c As IEntity2 = server.GetCustomer("CHOPS")

' Fetch a collection
Dim customers As IEntityCollection2 = serverTest.GetCustomers()
Configuration of the service
Below is the serviceModel element of the service config file. The settings below are illustrative and your own production service likely will use different values for various WCF settings. Please consult the WCF documentation in the MSDN library for details on the user elements.

<system.serviceModel>
    <bindings>
        <netTcpBinding>
            <binding name="RemoteConfig"
                closeTimeout="infinite"
                openTimeout="infinite"
                sendTimeout="infinite"
                receiveTimeout="infinite"
                maxBufferSize="65536000"
                maxReceivedMessageSize="65536000" />
        </netTcpBinding>
    </bindings>
    <services>
        <service name="Service.WCFExampleServer">
            <endpoint address="" binding="netTcpBinding" name="WCFExampleServer"
                      bindingConfiguration="RemoteConfig"
              contract="Interfaces.IWCFExample" />
            <host>
                <baseAddresses>
                    <add baseAddress="net.tcp://localhost:6543/WCFExampleServer" />
                </baseAddresses>
            </host>
        </service>
    </services>
</system.serviceModel> 
Configuration of the client
Below is the serviceModel element of the client config file. The settings below are illustrative and your own production service likely will use different values for various WCF settings. Please consult the WCF documentation in the MSDN library for details on the user elements.

<system.serviceModel>
    <bindings>
        <netTcpBinding>
            <binding name="RemoteConfig"
                closeTimeout="infinite"
                openTimeout="infinite"
                sendTimeout="infinite"
                receiveTimeout="infinite"
                maxBufferSize="65536000"
                maxReceivedMessageSize="65536000" />
        </netTcpBinding>
    </bindings>
    <client>
        <endpoint address="net.tcp://localhost:6543/WCFExampleServer"
                  name="WCFServer" binding="netTcpBinding"
                  bindingConfiguration="RemoteConfig"
                  contract="Interfaces.IWCFExample" />
    </client>
</system.serviceModel>

LLBLGen Pro Runtime Framework v3.5 documentation. ©2012 Solutions Design bv